#include "config.h"

#include <pthread.h>
#include <sys/time.h>

#define DBG_SUBSYS S_LIBYLIB

#include "ylock.h"
#include "sysutil.h"
#include "schedule.h"
#include "plock.h"
#include "lock_table.h"
#include "mem_pool.h"
#include "stack.h"

#define REUSE_LOCK 1

typedef struct {
        struct list_head hook;
        lock_table_t *lt;
        uint64_t idx;
        plock_t lock;
        int ring_base;
} ltable_entry_t;

static inline uint32_t __key_func(const void *k) {
        const uint64_t *idx = k;
        return (*idx) % 1048576;
}

static inline int __cmp_func(const void *s1, const void *s2) {
        const ltable_entry_t *e1 = s1;
        const uint64_t *idx = s2;
        if( e1->idx < *idx)
                return -1;
        else if (e1->idx > *idx)
                return 1;
        return 0;
}

static inline int __malloc_func(ltable_entry_t **e, lock_table_t *lt, uint64_t idx) {
        int ret;
        ltable_entry_t *_e;
        char *pname = NULL;
#if LOCK_DEBUG
        char lname[MAX_LOCK_NAME];

        pname = lname;
        sprintf(pname, "lt.%s.%ld", lt->name, idx);     //name is too long to cause assert.
#endif

        *e = NULL;

#if REUSE_LOCK
        if (list_empty_careful(&lt->free_list.list)) {
                if(likely(gloconf.ring)) {
                        int i;
                        ret = huge_malloc((void **)&_e, MEM_POOL_PAGE_SIZE);
                        if(likely(!ret)) {
                                for(i = 1; i< MEM_POOL_PAGE_SIZE / sizeof(ltable_entry_t); i++) {
                                        plock_init(&_e[i].lock, pname);
                                        INIT_LIST_HEAD(&_e[i].hook);
                                        _e[i].ring_base = 0;
                                }
                        }
                } else
                        ret = ymalloc((void **)&_e, sizeof(ltable_entry_t));
                
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                _e->ring_base = 1;
                plock_init(&_e->lock, pname);
                INIT_LIST_HEAD(&_e->hook);
        } else {
                struct list_head *pos = lt->free_list.list.next;
                _e = list_entry(pos, ltable_entry_t, hook);
                count_list_del_init(pos, &lt->free_list);
        }
#else
        ret = ymalloc((void **)&_e, sizeof(ltable_entry_t));
                if (unlikely(ret))
                        GOTO(err_ret, ret);

        _e->ring_base = 1;
        plock_init(&_e->lock, pname);
        INIT_LIST_HEAD(&_e->hook);
#endif

        _e->lt = lt;
        _e->idx = idx;

        *e = _e;
        return 0;
err_ret:
        return ret;

}

static inline void __free_func1(void *arg) {
        ltable_entry_t *e = arg;

        DBUG("free key %ju\n", e->idx);
#ifdef REUSE_LOCK
        if(!e->ring_base)
                YASSERT(0);
#endif
        // YASSERT(e != NULL);
        plock_destroy(&e->lock);
        if(gloconf.ring)
                huge_free((void **)&e);
        else
                yfree((void **)&e);
}

static inline void __free_func2(void *arg) {
        ltable_entry_t *e = arg;

        // DBUG("free key %ju\n", e->idx);

        count_list_add_tail(&e->hook, &e->lt->free_list);
}

int ltable_init(lock_table_t *lt, const char *name) {
        if(gloconf.ring)
                lt->ht = hash_create_table_huge(__cmp_func, __key_func, name);
        else
                lt->ht = hash_create_table(__cmp_func, __key_func, name);

        count_list_init(&lt->free_list);
        strcpy(lt->name, name);
        lt->inited = 1;

        return 0;
}

int ltable_destroy(lock_table_t *lt) {
        DBUG("lt %p\n", lt);

        if (lt->inited && lt->ht != NULL) {
                hash_destroy_table(lt->ht, __free_func1);

                struct list_head *pos, *n;
                list_for_each_safe(pos, n, &lt->free_list.list) {
                        ltable_entry_t *e = (ltable_entry_t *)pos; /*becareful of this op..*/
                        count_list_del_init(pos, &lt->free_list);
                        if(e->ring_base)
                                __free_func1(pos);      /*free a list from head*/
                }
        }

        return 0;
}

static int IO_FUNC ltable_lock(lock_table_t *lt, uint64_t idx, int is_shared) {
        int ret;
        ltable_entry_t *e;

        e = hash_table_find(lt->ht, (void *)&idx);
        if (e == NULL) {
                ret = __malloc_func(&e, lt, idx);
                if (unlikely(ret)) {
                        YASSERT(0);
                }

                ret = hash_table_insert(lt->ht, (void *)e, &e->idx, 0);
                if (unlikely(ret)) {
                        YASSERT(0);
                }
        }

        YASSERT(e != NULL);

        if (is_shared) {
                ret = plock_rdlock(&e->lock);
        } else {
                ret = plock_wrlock(&e->lock);
        }
        if (unlikely(ret)) {
                DWARN("idx %ju shared %d\n", idx, is_shared);
                GOTO(err_ret, ret);
        }

        DBUG("name %s key %ju shared %u e %p total %u %u\n",
              lt->name,
             idx,
             is_shared,
             e,
             lt->ht->num_of_entries,
             lt->free_list.count);

        return 0;
err_ret:
        return ret;
}

int ltable_wrlock(lock_table_t *lt, uint64_t idx) {
        return ltable_lock(lt, idx, 0);
}

int ltable_rdlock(lock_table_t *lt, uint64_t idx) {
        return ltable_lock(lt, idx, 1);
}

static IO_FUNC int ltable_trylock(lock_table_t *lt, uint64_t idx, int is_shared) {
        int ret;
        ltable_entry_t *e;

        e = hash_table_find(lt->ht, (void *)&idx);
        if (e == NULL) {
                ret = __malloc_func(&e, lt, idx);
                if (unlikely(ret)) {
                        YASSERT(0);
                }

                ret = hash_table_insert(lt->ht, (void *)e, &e->idx, 0);
                if (unlikely(ret)) {
                        YASSERT(0);
                }
        }

        YASSERT(e != NULL);

        if (is_shared) {
                ret = plock_tryrdlock(&e->lock);
        } else {
                ret = plock_trywrlock(&e->lock);
        }
        if (unlikely(ret)) {
                DBUG("idx %ju shared %d\n", idx, is_shared);
                GOTO(err_ret, ret);
        }

        DBUG("name %s key %ju shared %u e %p total %u %u\n",
              lt->name,
             idx,
             is_shared,
             e,
             lt->ht->num_of_entries,
             lt->free_list.count);

        return 0;
err_ret:
        return ret;
}

int ltable_trywrlock(lock_table_t *lt, uint64_t idx) {
        return ltable_trylock(lt, idx, 0);
}

int ltable_tryrdlock(lock_table_t *lt, uint64_t idx) {
        return ltable_trylock(lt, idx, 1);
}

int IO_FUNC ltable_unlock(lock_table_t *lt, uint64_t idx) {
        ltable_entry_t *e, *e2;

        e = hash_table_find(lt->ht, (const void *)&idx);
        YASSERT(e != NULL);

        DBUG("unlock key %ju\n", idx);

        plock_unlock(&e->lock);

        DBUG("name %s writer %d readers %d queue empty %d total %u %u\n",
              lt->name,
              e->lock.writer,
              e->lock.readers,
              list_empty_careful(&e->lock.queue),
              lt->ht->num_of_entries,
              lt->free_list.count);
        if (e->lock.writer == -1 && e->lock.readers == 0 && list_empty_careful(&e->lock.queue)) {
                hash_table_remove(lt->ht, &idx, (void **)&e2);
                YASSERT(e2 == e);
                if (e2 != NULL) {
                        YASSERT(e2->lt == lt);
                        YASSERT(e2->idx == idx);
#if REUSE_LOCK
                        __free_func2(e2);
#else
                        __free_func1(e2);
#endif
                }
        }

        return 0;
}

void * ltable_start_transaction(lock_table_t *lt) {
        stack_context_t *stack = malloc(sizeof(stack_context_t));
        if(!stack)
                return NULL;

        stack_init(stack);

        return stack;
}

void ltable_transaction_go(lock_table_t *lt, void *saction, uint64_t idx) {
        stack_push(saction, (void *)idx);
}

void ltable_revert_transaction(lock_table_t *lt, void *saction) {
        while(!stack_empty(saction)) {
                uint64_t idx = (uint64_t)stack_pop(saction);
                ltable_unlock(lt, idx);
        }

        free(saction);
}

void ltable_stop_transaction(lock_table_t *lt, void *saction) {
        while(!stack_empty(saction)) {
                (void)stack_pop(saction);
        }

        free(saction);
}